/*
 * Copyright 2007. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the GNU Lesser General Public License, Version 2. You
 * can find a copy of the license at:
 * 
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */
package com.sinai.mshab.client.ui.pages;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTMLTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.sinai.mshab.client.user.LDAPUser;
import com.sinai.mshab.client.util.ComponentRegistryFactory;
import com.sinai.mshab.client.exception.PageNotPopulateableException;
import com.sinai.mshab.client.ui.aggregate.AggregateFormComponent;
import com.sinai.mshab.client.ui.aggregate.FormData;
import com.sinai.mshab.client.ui.basic.Warning;

/**
 * The top level of a form. The <code>PageContainer</code> acts as a container
 * for the various {@link Page}s that a form consists of. The
 * <code>PageContainer</code> provides functionality such as page loading and
 * page navigation.
 * 
 * @author Arthur Kalmenson
 */
public class PageContainer extends VerticalPanel {

	/**
	 * The top panel.
	 */
	protected VerticalPanel northPanel;

	/**
	 * The left side bar.
	 */
	protected VerticalSidePanel westPanel;

	/**
	 * The right side bar.
	 */
	protected VerticalSidePanel eastPanel;

	/**
	 * The centre panel.
	 */
	protected VerticalPanel centrePanel;

	/**
	 * The panel which stores the content of the application, i.e. the Page.
	 */
	protected VerticalPanel contentPanel;

	/**
	 * The middle panel that contains the eastPanel, centrePanel and westPanel.
	 */
	protected HTMLTable middlePanel;

	/**
	 * The hpanel containing the page index buttons.
	 */
	protected HorizontalPanel page_index_buttons = new HorizontalPanel();

	/**
	 * A {@link HashMap} that keeps track of all the <code>Page</code>s.
	 * Pages are associated using the name of the page.
	 */
	protected HashMap pages = new HashMap();

	/**
	 * This arraylist keeps the String NAMES of the pages in order.
	 */
	protected ArrayList page_names_index = new ArrayList();

	/**
	 * The index of the current page.
	 */
	protected int cur_page = 0;

	/**
	 * The {@link ArrayList} to keep track of all the index buttons.
	 */
	protected ArrayList buttons_index;

	/**
	 * The pending page, will be loaded once user logs in.
	 */
	private static String pending_page = "";

	/**
	 * Create a new <code>PageContainer</code> and add it to the
	 * {@link RootPanel}.
	 */
	public PageContainer() {

		// setting up the main panel.
		this.setStyleName("main_panel");

		// instantiate panels
		northPanel = new VerticalPanel();
		middlePanel = new FlexTable();
		centrePanel = new VerticalPanel();
		contentPanel = new VerticalPanel();
		eastPanel = new VerticalSidePanel();
		westPanel = new VerticalSidePanel();

		// add the content to the centrePanel
		centrePanel.add(contentPanel);

		// add everything to the middle panel.
		middlePanel.setWidget(0, 0, westPanel);
		middlePanel.setWidget(0, 1, centrePanel);
		middlePanel.setWidget(0, 2, eastPanel);

		middlePanel.setStyleName("middlePanel");

		// Set the Styles for the Data Rows and Columns
		ColumnFormatter formatter = middlePanel.getColumnFormatter();
		formatter.setStyleName(0, "left_column");
		formatter.setStyleName(1, "middle_column");
		formatter.setStyleName(2, "right_column");

		CellFormatter cellFormatter = middlePanel.getCellFormatter();
		cellFormatter.setAlignment(0, 0, HasHorizontalAlignment.ALIGN_RIGHT,
				HasVerticalAlignment.ALIGN_TOP);
		cellFormatter.setAlignment(0, 1, HasHorizontalAlignment.ALIGN_CENTER,
				HasVerticalAlignment.ALIGN_TOP);
		cellFormatter.setAlignment(0, 2, HasHorizontalAlignment.ALIGN_LEFT,
				HasVerticalAlignment.ALIGN_TOP);

		// add the panels to the page container
		this.add(northPanel);
		this.add(middlePanel);

		// add CSS styles to the different panels.
		centrePanel.setStyleName("page");
		northPanel.setStyleName("northPanel");
		eastPanel.setStyleName("eastPanel");
		westPanel.setStyleName("westPanel");

		// make the east and west panels invisible by default.
		eastPanel.setVisible(false);
		westPanel.setVisible(false);
	}

	/**
	 * Add the {@link Page} <code>page</code>, named <code>page_name</code>.
	 * 
	 * @param page_name
	 *            the name of the <code>Page</code> being added.
	 * @param page
	 *            the <code>Page</code> to add to this
	 *            <code>PageContainer</code>.
	 */
	public void addPage(String page_name, Page page) {
		pages.put(page_name, page);
		page.setPageName(page_name);
		page_names_index.add(page_name);
	}

	/**
	 * Add the {@link Page} <code>page</code>, named <code>page_name</code>
	 * to index, <code>index</code>.
	 * 
	 * @param page_name
	 *            the name of the <code>Page</code> being added.
	 * @param page
	 *            the <code>Page</code> to add to this
	 *            <code>PageContainer</code>.
	 * @param index
	 *            the index where to insert the page in this
	 *            <code>PageContainer</code>.
	 */
	public void addPage(String page_name, Page page, int index) {
		pages.put(page_name, page);
		page.setPageName(page_name);
		page_names_index.add(index, page_name);
	}

	/**
	 * Load the first page, i.e. the start of the <code>PageContainer</code>.
	 */
	public void start() {
		loadPage((String) page_names_index.get(0));
		cur_page = 0;
	}

	/**
	 * Load the next page.
	 */
	public void next() {
		int next_page = cur_page + 1;
		if (next_page < page_names_index.size()) {
			loadPage(next_page);
			cur_page = next_page;
		}
	}

	/**
	 * Load the previous page.
	 */
	public void previous() {
		int prev_page = cur_page - 1;
		if (prev_page < page_names_index.size()) {
			loadPage(prev_page);
			cur_page = prev_page;
		}
	}

	/**
	 * Load the page at index <code>page_num</code> if <code>page_num</code>
	 * is an existing index and there is an associated page with that index.
	 * 
	 * @param page_num
	 *            the index of the <code>Page</code> to load.
	 */
	public void loadPage(int page_num) {
		// do some validation.
		if (page_num < page_names_index.size()) {
			String page_to_load = (String) page_names_index.get(page_num);
			if (page_to_load != null) {
				loadPage(page_to_load);
			}

			cur_page = page_num;
		}
	}

	/**
	 * Loads the page named <code>page</code> from the list of pages.
	 * 
	 * @param page
	 *            the name of the page to load.
	 */
	public void loadPage(String page) {

		Page new_page = (Page) pages.get(page);

		// if the Page doesn't exist, i.e. it is not in PageContainer, don't try
		// to load anything.
		if (new_page == null) {
			return;
		}

		// check if the new page is the same one as the current one, if it is,
		// exit.
		if (getCurrentPageName().equals(new_page)) {
			return;
		}

		// check if the user has sufficient permission to view this page.
		if (new_page.isSecurePage()) {
			String permission = LDAPUser.getUserPermission();
			if (permission == null || permission.equals("none")
					|| permission.equals("null")) {
				new Warning(
						"You do not have sufficient permission to view this page",
						300, 100);
				loadPage("Login");
				return;
			}
		}

		// call the other pages offload method
		Page curr = getCurrentPageInstance();
		curr.beforeAnotherPageLoaded();

		// remove the previous content
		contentPanel.clear();

		// save the index of the previous page.
		int previousPageIndex = cur_page;

		// update the PageContainerPanels.
		updatePageContainerPanels(getPageInstance(previousPageIndex));

		// update the history listener
		History.newItem(page);

		// create the new page if needed.
		if (!new_page.exists()) {
			new_page.initialize();
			new_page.setExists(true);
		}

		// update the cur_page
		for (int i = 0; i < page_names_index.size(); i++) {
			if (page_names_index.get(i).equals(page)) {

				// set the current page.
				cur_page = i;
				break;
			}
		}

		page_index_buttons.setVisible(new_page.isIndexable());

		// if the page is indexable, and thus should show the Page index at
		// the top, we have to set certain styles for the current Page.
		if (new_page.isIndexable()) {
			updatePageIndex(new_page, previousPageIndex);
		}

		// add this Page's panel items.
		// new_page.addPanelItems();

		updatePanelVisibility();

		// add the new Page.
		contentPanel.add(new_page);
		this.getEastPanel().activateClickedWidget();
		this.getWestPanel().activateClickedWidget();
	}

	/**
	 * Remove all the page specific {@link VerticalSidePanel} items from the
	 * <code>PageContainer</code>.
	 * 
	 * @param previousPage
	 *            the previous page.
	 */
	private void updatePageContainerPanels(Page previousPage) {

		// remove all the items from the specific panel.
		// westPanel.removePageLevelItems();
		// eastPanel.removePageLevelItems();
	}

	/**
	 * Sets visibility to false for panels that don't have any items in them,
	 * true for panels that do.
	 */
	private void updatePanelVisibility() {

		// if there are empty panels, make them invisible. If panels have
		// something in them, make them visible.
		if (eastPanel != null) {
			if (eastPanel.size() > 0) {
				eastPanel.setVisible(true);
			} else {
				eastPanel.setVisible(false);
			}
		}
		if (westPanel != null) {
			if (westPanel.size() > 0) {
				westPanel.setVisible(true);
			} else {
				westPanel.setVisible(false);
			}
		}
	}

	/**
	 * Update the page index to reflect to remove the page selected CSS from the
	 * previous page signified by <code>previousPageIndex</code>. We set the
	 * <code>new_page</code>'s CSS to signify that it is the currently select
	 * page.
	 * 
	 * @param new_page
	 *            the new page that is being viewed.
	 * @param previousPageIndex
	 *            the index of the previous page.
	 */
	private void updatePageIndex(Page new_page, int previousPageIndex) {

		if (buttons_index != null) {

			// set all buttons to default state
			for (int i = 0; i < buttons_index.size(); i++) {
				Button previous_index = (Button) buttons_index.get(i);
				previous_index.setStyleName("page_index_button");
			}

			// update the page index counter
			for (int i = 0; i < page_names_index.size()
					&& new_page.isIndexable(); i++) {
				if (page_names_index.get(i).equals(new_page.getPageName())) {

					// get the instance of the page we are moving away from.
					Page previous_page_instance = getPageInstance(previousPageIndex);

					// remove the "current page" CSS from the page we are
					// navigating away from if that page was in the index.
					if (previous_page_instance.isIndexable()) {
						Button previous_index = (Button) buttons_index
								.get(previousPageIndex);
						previous_index.setStyleName("page_index_button");
					}

					// set the CSS class on the current page index if the
					// the current page is in the index.
					if (new_page.isIndexable()) {
						Button cur_index = (Button) buttons_index.get(cur_page);
						cur_index.setStyleName("current_page_index_button");
					}
				}
			}
		}
	}

	/**
	 * Clears out all current pages so they need to be created again.
	 */
	public void reset() {
		ComponentRegistryFactory.getInstance().reset();
		FormData.getKey().getKeys().clear();

		Iterator i = pages.keySet().iterator();

		while (i.hasNext()) {
			String key = (String) i.next();
			Object o = pages.get(key);
			((Page) o).clear();
			o = null;
		}
	}

	/**
	 * Returns the index of the current page.
	 * 
	 * @return the index of the current page.
	 */
	public int getCurrentPage() {
		return cur_page;
	}

	/**
	 * Returns the name of the current page.
	 * 
	 * @return the name of the current page.
	 */
	public String getCurrentPageName() {
		return getPageName(getCurrentPage());
	}

	/**
	 * Returns the name of the page at provided index.
	 * 
	 * @return the name of the page at provided index.
	 */
	public String getPageName(int index) {
		return (String) page_names_index.get(index);
	}

	/**
	 * Returns the {@link Page} at index, <code>index</code>.
	 * 
	 * @param index
	 *            the index of the Page instance.
	 * @return the {@link Page} at index, <code>index</code>.
	 */
	public Page getPageInstance(int index) {

		return (Page) pages.get(page_names_index.get(index));
	}

	/**
	 * Returns the {@link Page} with the name, <code>name</code>.
	 * 
	 * @param name
	 *            the name of the Page instance.
	 * @return the {@link Page} with the name, <code>name</code>.
	 */
	public Page getPageInstance(String name) {

		return (Page) pages.get(name);
	}

	/**
	 * Returns the instance of the currently loaded page.
	 * 
	 * @return the instance of the currently loaded page.
	 */
	public Page getCurrentPageInstance() {
		String name = (String) page_names_index.get(getCurrentPage());
		return (Page) pages.get(name);
	}

	/**
	 * Returns an {@link ArrayList} of all the reportable pages.
	 * 
	 * @return all the reportable pages.
	 * @see Page#isReportable()
	 */
	public ArrayList getReportablePages() {
		ArrayList reportablePages = new ArrayList();

		for (int i = 0; i < page_names_index.size(); i++) {
			Page page = getPageInstance(i);
			if (page.isReportable()) {
				reportablePages.add(page);
			}
		}
		return reportablePages;
	}

	/**
	 * Returns an {@link ArrayList} of all the content form pages.
	 * 
	 * @return an {@link ArrayList} of all the content form pages.
	 * @see Page#isContentForm()
	 */
	public ArrayList getContentFormPages() {
		ArrayList contentPages = new ArrayList();

		for (int i = 0; i < page_names_index.size(); i++) {
			Page page = getPageInstance(i);
			if (page.isContentForm()) {
				contentPages.add(page);
			}
		}
		return contentPages;
	}

	/**
	 * Create a page index at the top of the Page container.
	 */
	public void addPageIndex() {
		page_index_buttons.setStyleName("pageIndex");

		// instantiate the button_index
		buttons_index = new ArrayList();

		// for the number of pages...
		for (int i = 0; i < page_names_index.size(); i++) {

			Page the_page = (Page) pages.get((String) page_names_index.get(i));
			if (the_page.isIndexable()) {

				// create a button and give it the page_index_button CSS class
				Button page_link = new Button();
				page_link.setText("" + (i + 1));
				page_link.addStyleName("page_index_button");
				page_link.setTitle("Go to page " + (i + 1));

				// add its ClickListener and add it to the top panel.
				page_link.addClickListener(new PageIndexButtonListener());
				buttons_index.add(page_link);
				page_index_buttons.add(page_link);
			}
		}

		// add the index to the top of the centre panel.
		centrePanel.insert(page_index_buttons, 0);
	}

	/**
	 * Adds the logo located at <code>path</code> to the northPanel.
	 * 
	 * @param path
	 *            the path to the logo.
	 */
	public void addLogo(String path) {
		addLogo(path, northPanel);
	}

	/**
	 * Adds the logo located at <code>path</code> to the {@link VerticalPanel}
	 * parent.
	 * 
	 * @param path
	 *            the path to the logo.
	 * @param parent
	 *            where to add this logo to.
	 */
	public void addLogo(String path, VerticalPanel parent) {
		Image logo = new Image(path);
		logo.addStyleName("logo");
		parent.add(logo);
		System.out.println("Adding logo");
	}

	/**
	 * Insert the logo located at <code>path</code> into the
	 * {@link VerticalPanel} parent at index <code>index</code>.
	 * 
	 * @param path
	 *            the path to the logo.
	 * @param parent
	 *            where to add this logo to.
	 * @param index
	 *            where to insert the logo.
	 */
	public void addLogo(String path, VerticalPanel parent, int index) {
		Image logo = new Image(path);
		logo.addStyleName("logo");
		parent.insert(logo, index);
		System.out.println("Adding logo");
	}

	/**
	 * Submits all the pages in the container.
	 */
	public void submitAll() {

		Iterator page_names = pages.keySet().iterator();
		while (page_names.hasNext()) {
			Page page = (Page) pages.get((String) page_names.next());

			// only if the page is populateable, it can submit information.
			if (page.isContentForm()) {

				page.submit();
			}
		}
		FormData.getInstance().submitData();
	}

	/**
	 * Retrieves all the patient information from the server.
	 */
	public void getInformation() {
		FormData.getInstance().populateFormData(this);
	}

	/**
	 * Tells all pages that are populateable to populate themselves.
	 */
	public void populate() {

		Iterator page_names = pages.keySet().iterator();

		while (page_names.hasNext()) {
			Page p = (Page) pages.get(page_names.next());

			// insure that this Page can be populated.
			if (p.isPopulateable()) {
				try {
					p.populate();
				} catch (PageNotPopulateableException e) {
					// TODO print this to the error log
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * @see AggregateFormComponent#toReport()
	 */
	public VerticalPanel toReport() {
		VerticalPanel printableVersion = new VerticalPanel();

		for (int i = 0; i < page_names_index.size(); i++) {
			Page page = (Page) pages.get(page_names_index.get(i));

			// if page was used for user input, we want to run a report on it.
			if (page.isReportable()) {
				printableVersion.add(page.toReport());
			}
		}

		return printableVersion;
	}

	/**
	 * Returns this <code>PageContainer</code>'s centre panel.
	 * 
	 * @return this <code>PageContainer</code>'s centre panel.
	 */
	public VerticalPanel getCentrePanel() {
		return centrePanel;
	}

	/**
	 * Returns this <code>PageContainer</code>'s west panel.
	 * 
	 * @return this <code>PageContainer</code>'s west panel.
	 */
	public VerticalSidePanel getWestPanel() {
		return westPanel;
	}

	/**
	 * Returns this <code>PageContainer</code>'s east panel.
	 * 
	 * @return this <code>PageContainer</code>'s east panel.
	 */
	public VerticalSidePanel getEastPanel() {
		return eastPanel;
	}

	/**
	 * Returns this {@literal PageContainer}'s north panel.
	 * 
	 * @return this {@literal PageContainer}'s north panel.
	 */
	public VerticalPanel getNorthPanel() {
		return northPanel;
	}

	/**
	 * A {@link ClickListener} that is attached to page index buttons. When one
	 * of the buttons is clicked, the specific {@link Page} will be loaded.
	 * 
	 * @author Arthur Kalmenson
	 */
	private class PageIndexButtonListener implements ClickListener {
		public void onClick(Widget sender) {

			if (getCurrentPageInstance().validate()) {
				// get the label on this button (i.e. the page number)
				int page_to_load = Integer
						.parseInt(((Button) sender).getHTML());

				// subtract one because the text on the Buttons starts from 1
				// instead of 0.
				page_to_load--;

				loadPage(page_to_load);
			}
		}
	}

	/**
	 * Returns whether there are any pending pages.
	 * 
	 * @return true if there is a pending page.
	 */
	public static boolean hasPendingPage() {
		return getPending_page().length() > 0;
	}

	/**
	 * @param pending_page
	 *            the pending_page to set
	 */
	public static void setPending_page(String p) {
		pending_page = p;
	}

	/**
	 * @return the pending_page
	 */
	public static String getPending_page() {
		return pending_page;
	}
}
